package org.artifact.core.db;

import cn.hutool.core.util.ObjectUtil;
import cn.hutool.log.Log;
import cn.hutool.log.LogFactory;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoCursor;
import com.mongodb.client.MongoDatabase;
import com.mongodb.client.model.DeleteOneModel;
import com.mongodb.client.model.Filters;
import com.mongodb.client.model.ReplaceOneModel;
import com.mongodb.client.model.ReplaceOptions;
import org.artifact.core.cache.CacheEntry;
import org.artifact.core.plugin.mongo.MongoPlugin;
import org.artifact.core.server.ServerPlugins;
import org.bson.Document;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;

public abstract class MongoDBDao<T extends AbstractEntity<T>> extends AbstractDao<T> {

    static final Log log = LogFactory.get(MongoDBDao.class);

    protected MongoDatabase getMongoDatabase() {
        return ServerPlugins.me().getPlugin(MongoPlugin.class).getMongoDatabase();
    }

    @Override
    protected boolean onSave(T t) {
        Map map = (Map) t.toSave().write();
        getCollection().replaceOne(new Document().append(getPKFieldName(), t.pk()), new Document(map), new ReplaceOptions().upsert(true));
        return true;
    }

    @Override
    protected boolean onRemove(T t) {
        getCollection().deleteOne(Filters.eq(getPKFieldName(), t.pk()));
        return true;
    }
    @Override
    protected boolean onSave(T... t) {

        List<ReplaceOneModel<Document>> requests = new ArrayList<ReplaceOneModel<Document>>();
        for (T t1 : t) {
            Map map = (Map) t1.toSave().write();
            requests.add(new ReplaceOneModel<Document>(new Document().append(getPKFieldName(), t1.pk()), new Document(map), new ReplaceOptions().upsert(true)));
        }
        getCollection().bulkWrite(requests);
        return true;
    }

    @Override
    protected boolean onRemove(T... t) {
        List<DeleteOneModel<Document>> requests = new ArrayList<DeleteOneModel<Document>>();
        for (T t1 : t) {
            requests.add(new DeleteOneModel<>(Filters.eq(getPKFieldName(), t1.pk())));
        }
        getCollection().bulkWrite(requests);
        return true;
    }

    @Override
    protected boolean onSave(Collection<T> coll) {
        List<ReplaceOneModel<Document>> requests = new ArrayList<ReplaceOneModel<Document>>();
        for (T t1 : coll) {
            Map map = (Map) t1.toSave().write();
            requests.add(new ReplaceOneModel<Document>(new Document().append(getPKFieldName(), t1.pk()), new Document(map), new ReplaceOptions().upsert(true)));
        }
        getCollection().bulkWrite(requests);
        return true;
    }

    @Override
    protected boolean onRemove(Collection<T> coll) {
        List<DeleteOneModel<Document>> requests = new ArrayList<DeleteOneModel<Document>>();
        for (T t1 : coll) {
            requests.add(new DeleteOneModel<>(Filters.eq(getPKFieldName(), t1.pk())));
        }
        getCollection().bulkWrite(requests);
        return true;
    }

//    @Override
//    protected boolean finishSaveCache() {
//        List<ReplaceOneModel<Document>> requests = new ArrayList<ReplaceOneModel<Document>>();
//        for (Entry<Object, T> e : cachedUpdates.entrySet()) {
//            T bean = e.getValue();
//            Map map = (Map) bean.write();
//            requests.add(new ReplaceOneModel<Document>(new Document().append(getPKFieldName(), e.getKey()), new Document(map), new ReplaceOptions().upsert(true)));
//        }
//        getCollection().bulkWrite(requests);
//        return true;
//    }
//
//    @Override
//    protected boolean finishDeleteCache() {
//        List<DeleteOneModel<Document>> requests = new ArrayList<DeleteOneModel<Document>>();
//        for (Entry<Object, T> e : cachedDeletes.entrySet()) {
//            requests.add(new DeleteOneModel<>(Filters.eq(getPKFieldName(), e.getKey())));
//        }
//        getCollection().bulkWrite(requests);
//        return true;
//    }


    @Override
    public boolean existsTable() {
        return getMongoDatabase().listCollectionNames().into(new ArrayList<String>()).contains(getTableName());
    }

    @Override
    public List<T> findAll() {
        return resultList(() -> {
            return getCache().getAllOrFunction(()->{
                List<CacheEntry<Object, T>> cacheEntrys = new ArrayList<>();
                MongoCollection<Document> collection = getCollection();
                MongoCursor<Document> mongoCursor = collection.find().iterator();
                while (mongoCursor.hasNext()) {
                    T entity = createEntity().read(mongoCursor.next());
                    cacheEntrys.add(new CacheEntry<>(entity.pk(),entity));
                }
                return cacheEntrys;
            });
        }, t -> true);
    }

    @Override
    public T findByPrimaryKey(Object primaryKey) {
        return resultFirst(() -> {
            return getCache().getOrFunction(primaryKey,k -> {
                MongoCollection<Document> collection = getCollection();
                Document document = collection.find(Filters.eq(getPKFieldName(), primaryKey)).first();
                if (document != null) {
                    return createEntity().read(document);
                }
                return null;
            });
        }, t -> ObjectUtil.equal(t.pk(), primaryKey));
    }

    protected MongoCollection<Document> getCollection() {
        return getMongoDatabase().getCollection(getTableName());
    }
}
